package utils

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"math"
	"reflect"
	"strconv"
	"strings"
	"sync/atomic"
	"time"

	"gitee.com/liuyun-luo/MagicStreamMovies/Server/global"
	"gitee.com/liuyun-luo/MagicStreamMovies/Server/models"
	"github.com/go-redis/redis"
)

// Redis的消息队列List的消息结构
type Message struct {
	ID      string    `json:"id"`
	Content string    `json:"content"`
	Created time.Time `json:"created"`
}

// 原子计数器
type Counter struct {
	Count int64
}

func (c *Counter) Get() int64 {
	new_count := atomic.AddInt64(&c.Count, 1)

	//如果新的计数器值大于最大int64的一半，则重置为0
	if new_count > (math.MaxInt64 / 2) {
		atomic.StoreInt64(&c.Count, 0)
	}
	return new_count
}

// 全局原子计数器
var counter Counter

func NewMassage(content string, created time.Time) *Message {
	return &Message{
		ID:      strconv.FormatInt(time.Now().UnixNano(), 10) + strconv.FormatInt(counter.Get(), 10),
		Content: content,
		Created: created,
	}
}

type Queue struct {
	client *redis.Client
	Name   string
}

func NewQueue(name string) *Queue {
	return &Queue{client: global.RedisDB, Name: name}
}

// 生产者：发送消息
func (q *Queue) Push(msg Message) error {
	jsonData, err := json.Marshal(msg)
	if err != nil {
		return err
	}
	// 使用 LPush 将消息放入列表头部
	return q.client.LPush(q.Name, jsonData).Err()
}

// 消费者：处理消息
func (q *Queue) Pop(ctx context.Context) (Message, error) {
	for {
		select {
		case <-ctx.Done():
			log.Println("消费者退出")
			return Message{}, ctx.Err()
		default:
			// 使用 BRPop 阻塞地从列表尾部取消息，超时时间设为10秒
			result, err := q.client.BRPop(time.Second*10, q.Name).Result()
			if err != nil {
				log.Println("从队列获取消息失败:", err)
				return Message{}, err
			}

			var msg Message
			// result[1] 是消息内容
			if err := json.Unmarshal([]byte(result[1]), &msg); err != nil {
				log.Println("解析消息失败:", err, "原始数据:", result[1])
				return Message{}, err
			}
			return msg, nil
		}
	}
}

// StructToHash 自动将结构体转为 Redis 哈希所需的 map[string]interface{}
// 支持基础类型（int、string、bool 等）
// 和
// 嵌套结构体（嵌套字段会转为 "parent_field.child_field" 格式）
func StructToHash(s interface{}) (map[string]interface{}, error) {
	hashData := make(map[string]interface{})
	val := reflect.ValueOf(s)

	// 若传入的是指针，先取指向的值
	if val.Kind() == reflect.Ptr {
		val = val.Elem()
	}
	// 确保传入的是结构体（非指针、非其他类型）
	if val.Kind() != reflect.Struct {
		return nil, fmt.Errorf("输入不是结构体或结构体指针")
	}

	typ := val.Type() // 获取结构体类型信息
	for i := 0; i < val.NumField(); i++ {
		fieldTyp := typ.Field(i)   // 字段类型（如 PostID 的类型是 int64）
		fieldVal := val.Field(i)   // 字段值（如 1001）
		fieldName := fieldTyp.Name // 字段名（如 "PostID"）

		// 处理嵌套结构体：若字段是结构体，递归转为扁平字段（如 User.Name → "user_name"）
		if fieldVal.Kind() == reflect.Struct {
			nestedHash, err := StructToHash(fieldVal.Interface())
			if err != nil {
				return nil, err
			}
			// 嵌套字段名转为“父字段名_子字段名”（如 User.Name → "user_name"）
			parentName := strings.ToLower(fieldName)
			for nestedKey, nestedVal := range nestedHash {
				flatKey := parentName + "_" + nestedKey
				hashData[flatKey] = nestedVal
			}
			continue
		}

		// 基础类型：字段名转为小写（如 "PostID" → "post_id"），值直接存入
		hashKey := strings.ToLower(strings.ReplaceAll(fieldName, "ID", "_id")) // 统一字段名格式（如 PostID → post_id）
		hashData[hashKey] = fieldVal.Interface()
	}

	return hashData, nil
}

// 电影热度排行榜
type MoviesPopularityZSet struct {
	client *redis.Client
	Name   string
}

func NewMoviesPopularityZSet(client *redis.Client, name string) *MoviesPopularityZSet {
	return &MoviesPopularityZSet{
		client: client,
		Name:   name,
	}
}

// 计算电影热度，并把电影添加到ZSet，添加成功就返回电影当前排名和分数
func (this *MoviesPopularityZSet) Add(movie *models.Movie) (int64, float64, error) {
	//计算分数
	fraction := GetMoviePopularity(movie)
	id := strconv.FormatUint(uint64(movie.ID), 10)
	//计算电影热度，并把电影添加到ZSet
	if err := this.client.ZAdd(this.Name, redis.Z{
		Score:  fraction,
		Member: id,
	}).Err(); err != nil {
		return 0, 0, fmt.Errorf("把电影添加到ZSet失败,err: %s", err.Error())
	}

	//获取排名
	ranking, err := this.client.ZRevRank(this.Name, id).Result()
	if err != nil {
		return 0, 0, fmt.Errorf("获取电影排名失败,err: %s", err.Error())
	}

	return ranking, fraction, nil
}

// 返回指定排名区间的电影的id、分数
func (this *MoviesPopularityZSet) GetRange(start, stop int64) ([]uint, []float64, error) {
	movies_popularity_list, err := this.client.ZRevRangeWithScores(this.Name, start, stop).Result()
	if err != nil {
		return nil, nil, fmt.Errorf("获取电影排名失败,err: %s", err.Error())
	}
	//热度前50的电影的id
	var movie_ids []uint
	var movie_scores []float64
	for _, movie := range movies_popularity_list {
		id, _ := strconv.ParseUint(movie.Member.(string), 10, 64)
		movie_ids = append(movie_ids, uint(id))
		movie_scores = append(movie_scores, movie.Score)
	}

	return movie_ids, movie_scores, nil
}
